﻿
using SimpleTerminal.Common;
using System;
using System.Collections;
using System.Drawing;
using static SimpleTerminal.TerminalControl.Line;

namespace SimpleTerminal.TerminalControl
{
    public class TerminalContainer
    {
        private readonly ArrayList list;
        private readonly ArrayList scrollBack;
        private int offset = 0;
        private int consoleHeight = 0;
        public int maxSize = 0;
        private int size = 0;
        //private int currentLineIndex = 0;
        private int offsetFixed = 0;
        private int scrollingBottom = 0;
        private int scrollingTop = 0;

        private int consoleWidth = 0;

        //public object _writelock = new object();

        private int updateTopIndex = -1;
        private int updateBottomIndex = -1;


        private int screenOffset = 0;

        public class CharacterMask
        {
            public const byte None = 0x00;
            public const byte Half1 = 0x02;
            public const byte Half2 = 0x04;
            public const byte Underline = 0x08;
            public const byte RevolvedVideo = 0x10;
            public const byte INVALID = 0x20;
            public const byte Empty = 0x80;
        };

        private CursorAttributes cursor = new CursorAttributes();

        public delegate void CountChangedHandler(int offset);
        public event CountChangedHandler OnCountChanged;


        public void SetUpdateRegion(int top, int bottom)
        {
            if (top < updateTopIndex || updateTopIndex == -1)
                updateTopIndex = top;
            if (bottom > updateBottomIndex || updateBottomIndex == -1)
                updateBottomIndex = bottom;
        }
        public void Invalid()
        {
            Invalid(0, consoleHeight - 1);
        }

        public void Invalid(int top, int bottom)
        {
            for (int i = top; i <= bottom; i++)
            {
                Invalid(i);
            }
        }


        public void Invalid(int index)
        {
            this[ConsoleOffset + index].State = CommonDefine.PAINT_INVALID;
        }
        public void InvalidCharacter(int rows, int cols)
        {
            this[rows].State = CommonDefine.PAINT_INVALID;
          //  this[rows][cols].mask |= CommonDefine.PAINT_INVALID;
        }
        public void InvalidAbs(int top, int len)
        {
            for (int i = top; i < top + len; i++)
            {
                this[i].State = CommonDefine.PAINT_INVALID;
            }
        }

        public int ScrollBackSize
        {
            set
            {
                size = value;
                TrimScrollBack();
            }
            get
            {
                return size;
            }
        }

        public void Clear()
        {
            list.Clear();
        }



        private void OnCountChangedEvent()
        {
            OnCountChanged?.Invoke(list.Count);
        }

        public void ReSetUpdateRegion()
        {
            updateTopIndex = -1;
            updateBottomIndex = -1;
        }

        public int UpdateTopIndex { get => updateTopIndex; set => updateTopIndex = value; }
        public int UpdateBottomIndex { get => updateBottomIndex; set => updateBottomIndex = value; }


        /// <summary>
        /// 判断字符是否是全宽字符
        /// </summary>
        /// <param name="ucs"></param>
        /// <returns></returns>
        private bool IsWide(char ucs)
        {
            if (ucs < 0x1100)
                return false;
            return
              (ucs >= 0x1100 && ucs <= 0x115f) || /* Hangul Jamo */
              (ucs >= 0x2e80 && ucs <= 0xa4cf && (ucs & ~0x0011) != 0x300a &&
               ucs != 0x303f) ||                  /* CJK ... Yi */
              (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
              (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
              (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
              (ucs >= 0xff00 && ucs <= 0xff5f) || /* Fullwidth Forms */
              (ucs >= 0xffe0 && ucs <= 0xffe6);
        }

        public void Write(char cc)
        {
            //Console.WriteLine(cc);

            //while (consoleWidth <= 20) ;
            // cc = Charset.Winucs.GetUcs(cc);
            if (IsWide(cc))
            {
                /**
                 * 如果当前宽度无法写入字符则换入下一行 
                 */
                if (consoleWidth <= cursor.X + 1)
                {
                    ScrollToNext();
                    cursor.X = 0;
                }
                Line line = CurrentLine();

                FullCharacter(line[cursor.X], cc, CharacterMask.Half1);
                FullCharacter(line[cursor.X + 1], cc, CharacterMask.Half2);
                /*
                 * 这里必须直接使用cursor.X而不能使用CursorIndex
                 * 因为CursorIndex超过边界后会重置输入点为最后一个字符
                 * 而我们这里需要依靠是否超出边界来判断是否需要换行
                 */
                cursor.X += 2;
            }
            else
            {
                /*
                * 如果当前宽度无法写入字符则换入下一行 
                */
                if (consoleWidth <= cursor.X)
                {
                    ScrollToNext();
                    cursor.X = 0;
                }
                Line line = CurrentLine();
                FullCharacter(line[cursor.X], cc, 0x00);
                cursor.X += 1;
            }
            //因为使用了cursor.X，必须手动设置更新区域
            //SetUpdateRegion(CursorLineIndex, CursorLineIndex);
            Invalid(Cursor.Y);

        }

        /// <summary>
        /// 获取光标的当前位置
        /// </summary>
        public int CursorIndex
        {
            get
            {
                return cursor.X;
            }
            set
            {
                if (value < 0)
                {
                    cursor.X = 0;
                }
                else if (value >= consoleWidth)
                {
                    cursor.X = consoleWidth - 1;
                }
                else
                {
                    cursor.X = value;
                }
                Invalid(cursor.Y);
            }
        }


        public void MoveCursorEraseCharacters(int offset)
        {
            //if (offset > CursorIndex)
            //    EraseCharacters(offset - CursorIndex);
            //if()
            CursorIndex = offset;
        }

        public TerminalContainer(int width, int height, int size)
        {
            this.size = size;
            maxSize = size + height;

            list = new ArrayList(maxSize * 2);
            //scrollBack = new ArrayList(maxSize);

            cursor.BackgroundColor = defaultBackground;
            cursor.ForegroundColor = defaultForeground;

            ConsoleWidth = width;
            //这个应该放置在list初始化后面
            Height = height;

            //设置可滚动区域
            SetScrollingRegion(0, height - 1);
        }


        public bool NoScrollBack()
        {
            return size == 0;
        }

        public void SetScrollingRegion(int top, int bottom)
        {
            //设置可滚动区域的顶部索引值
            scrollingTop = Math.Max(top, 0);
            //设置可滚动区域的底部索引值
            scrollingBottom = Math.Min(bottom, consoleHeight - 1);
            //将当前行的光标设置为第一行（也就是索引0行）
            cursor.Y = 0;
        }


        public void Reset()
        {
            list.Clear();
            offset = 0;
            offsetFixed = 0;
            int height = consoleHeight;
            consoleHeight = 0;
            Height = height;
            ResetCursor(false);
            //Invalid(0, height - 1);
            OnCountChangedEvent();
        }
        public void ResetCursor(bool onlyAttributes)
        {
            cursor.CharacterStyle = 0;
            cursor.HeightLight = false;
            cursor.ForegroundColor = defaultForeground;
            cursor.BackgroundColor = defaultBackground;
            cursor.RevolvedVideo = false;
            if (!onlyAttributes)
            {
                cursor.X = 0;
                cursor.Y = 0;
            }
        }
        private void Add(Line element)
        {

            list.Add(element);
            TrimScrollBack();
        }

        private void Insert(int index, Line element)
        {
            list.Insert(index, element);
            TrimScrollBack();
        }

        public int Count
        {
            get
            {
                return list.Count - offsetFixed;
            }
        }


        public int MaximumSize
        {
            get
            {
                return size + consoleHeight;
            }
        }

        public int MoveConsoleOffset()
        {
            return MoveConsoleOffset(1);
        }

        public int MoveConsoleOffset(int n)
        {
            ConsoleOffset += n;

            return offset;
        }

        public void PostCursorLine(int line)
        {
            int newValue = cursor.Y + line;
            if (newValue < scrollingTop)
                newValue = scrollingTop;
            CursorLineIndex = newValue;
        }

        /// <summary>
        /// 翻转视频显示
        /// </summary>
        /// <param name="val"></param>
        public void ReverseVideo(bool val)
        {
            cursor.RevolvedVideo = val;
        }

        public void ReverseLinefeed()
        {
            if (CursorLineIndex == ScrollingTop)
            {
                list.Insert(offset + scrollingTop, CreateLine());
                list.RemoveAt(offset + scrollingBottom + 1);
                Invalid(scrollingTop, scrollingBottom);
            }
            else
            {
                PostCursorLine(-1);
            }
        }

        public int ConsoleOffset
        {
            get
            {
                return offset;
            }
            set
            {
                offset = value;
            }
        }

        public bool CursorInEnd
        {
            get
            {
                return CurrentLine().Characters.Count - 1 <= CursorIndex;
            }
        }

        public void ClearScrollBack()
        {

            if (offset > 0)
            {
                list.RemoveRange(0, offset);
                offset = 0;
            }

        }
        private void TrimScrollBack()
        {

            if (offset > size)
            {
                list.RemoveRange(0, offset - size);
                offset = size;
            }
        }

        public int Height
        {
            get => consoleHeight;
            set
            {

                int oldHeight = consoleHeight;
                consoleHeight = value;
                if (value > oldHeight) //扩大窗体
                {
                    int n = value - oldHeight;
                    if (n > ConsoleOffset)
                    {
                        n = ConsoleOffset;
                    }
                    ConsoleOffset -= n;
                    cursor.Y += n;
                    //新增的行则填充Line类

                    for (int i = oldHeight + n; i < value; i++)
                    {
                        Add(CreateLine());
                    }
                }
                else if (value < oldHeight)//缩小窗体
                {
                    if (cursor.Y >= value)
                    {
                        int remove = oldHeight - cursor.Y - 1;
                        //删除空行
                        list.RemoveRange(ConsoleOffset + cursor.Y, remove);

                        int newValue = value - 1;
                        offset = CurrentLineAdsIndex - newValue;
                        cursor.Y = newValue;
                    }
                    else
                    {
                        int remove = oldHeight - value;
                        list.RemoveRange(offset + value, remove);

                    }
                }

                /**
                * 此处旨在避免使用无滚动区的时候强制删除多余行
                */
                TrimScrollBack();
                /**
                 * 如果如果滚动区是全屏则继续调整为全屏
                 */
                if (scrollingTop == 0 && scrollingBottom == oldHeight - 1)
                {
                    scrollingTop = 0;
                    scrollingBottom = value - 1;
                }

                Invalid();
                OnCountChangedEvent();
            }
        }

        public int CursorLineIndex
        {
            get => cursor.Y;
            set
            {
                int old = cursor.Y;
                /**
                 * 允许光标所在行超过可滚动区域
                 */
                if (value > consoleHeight - 1)
                    cursor.Y = consoleHeight - 1;
                else if (value < 0)
                    cursor.Y = 0;
                else
                    cursor.Y = value;


                Invalid(old);
                Invalid(cursor.Y);

            }
        }

        public int ConsoleWidth
        {
            get
            {
                return consoleWidth;
            }
            set
            {
                consoleWidth = value;
            }
        }


        public int CurrentLineAdsIndex
        {
            get
            {
                return ConsoleOffset + cursor.Y;
            }
        }

        public int BottomAdsIndex
        {
            get
            {
                return ConsoleOffset + consoleHeight - 1;
            }
        }

        public int Maximum
        {
            get
            {
                return ConsoleOffset + Height - 1;
            }
        }

        public int Minimum
        {
            get
            {
                return 0;
            }
        }

        public int ScrollingBottom { get => scrollingBottom; }

        public int ScrollingTop { get => scrollingTop; }

        private Line CreateLine()
        {
            return new Line(this);
        }

        public void InsertBlankLine(int n)
        {

            for (int i = 0; i < n; i++)
            {
                list.Insert(offset + cursor.Y, CreateLine());
            }
            list.RemoveRange(offset + scrollingBottom + 1, n);
            Invalid(cursor.Y, scrollingBottom);

        }

        public void InsertBlankCharceters(int n)
        {

            Line line = CurrentLine();
            for (int i = 0; i < n; i++)
            {
                Character character = new Character();
                FullCharacter(character, ' ', 0x00);
                line.Characters.Insert(cursor.X, character);
            }

        }

        public Line CurrentLine()
        {
            return (Line)list[offset + cursor.Y];
        }

        public void Translation(int top)
        {

            for (int i = 0; i < top; i++)
            {
                list.Insert(offset + scrollingTop, CreateLine());
            }
            list.RemoveRange(offset + scrollingBottom + 1, top);
            Invalid(scrollingTop, scrollingBottom);

        }

        public void DelectLine(int top)
        {

            list.RemoveRange(offset + cursor.Y, top);

            for (int i = 0; i < top; i++)
            {
                list.Insert(offset + scrollingBottom - top + 1, CreateLine());
            }
            Invalid(cursor.Y, scrollingBottom);

        }
        public int ScreenOffset = 0;


        public ITerminalLog LogInstance
        {
            get;
            set;
        }
        public bool LogWrite
        {
            get;
            set;
        }
        public void ScrollToNext()
        {
            //如果当前行已经是可滚动区域的最后一行

            if (LogWrite && LogInstance != null)
            {
                LogInstance.Write(CurrentLine().ToString());
            }

            if (cursor.Y == scrollingBottom)
            {


                //可滚动区域是全屏，则直接在队列的末尾追加内容即可
                if (scrollingTop == 0 && scrollingBottom == Height - 1)
                {
                    MoveConsoleOffset();
                    Add(CreateLine());
                    ScreenOffset++;
                    Invalid(Math.Max(scrollingBottom - 1, 0));
                }
                //可滚动区域的顶部就在终端第一行
                else if (scrollingTop == 0)
                {
                    MoveConsoleOffset();
                    Insert(ConsoleOffset + scrollingBottom, CreateLine());
                    Invalid(0, scrollingBottom);
                }
                //可滚动区顶部和底部都与终端窗口不同
                else
                {
                    list.RemoveAt(ConsoleOffset + scrollingTop);
                    list.Insert(ConsoleOffset + scrollingBottom, CreateLine());
                    Invalid(scrollingTop, scrollingBottom);
                }
            }
            //OnCountChangedEvent();
            else
            {
                CursorLineIndex++;
            }
        }


        public void EraseLine(int type = 1)
        {
            // Erase line (default: from cursor to end of line).
            // ESC[1 K: erase from start of line to cursor.
            // ESC[2 K: erase whole line.
            Line line = CurrentLine();
            switch (type)
            {
                case 0:
                    line.Adjustment(CursorIndex - 1, true);
                    EraseCharacter(line.GetCharacter(CursorIndex));
                    //for (int i = CursorIndex; i < consoleWidth; i++)
                    //{

                    //    EraseCharacter(line.GetCharacter(i));
                    //EraseCharacter(line.GetCharacter(i));
                    // }
                    break;
                case 1:
                    for (int i = 0; i <= CursorIndex; i++)
                    {
                        EraseCharacter(line[i]);
                    }
                    break;
                case 2:
                    for (int i = 0; i < consoleWidth; i++)
                    {
                        EraseCharacter(line[i]);
                    }
                    break;
            }
        }
        public void EraseDisplay(int type = 1)
        {

            switch (type)
            {
                case 3:
                    ClearScrollBack();
                    break;
                case 2:
                    for (int i = ConsoleOffset; i < ConsoleOffset + Height; i++)
                    {
                        this[i].Adjustment(-1, true);
                        //for (int j = 0; j < consoleWidth; j++)
                        //    EraseCharacter(this[i][j]);
                    }
                    break;
                case 1:
                    for (int i = CurrentLineAdsIndex; i <= BottomAdsIndex; i++)
                        for (int j = 0; j < consoleWidth; j++)
                            EraseCharacter(this[i][j]);
                    break;
                case 0:
                    RemoveCursorToEnd();
                    for (int i = CurrentLineAdsIndex + 1; i <= BottomAdsIndex; i++)
                        for (int j = 0; j < consoleWidth; j++)
                            EraseCharacter(this[i][j]);
                    break;
            }
            Invalid();

        }



        public void DeleteCharacters(int len)
        {

            Line line = CurrentLine();
            if (len > line.Characters.Count)
            {
                len = line.Characters.Count;
            }
            Log.Debug("DeleteCharacters", "" + len);
            line.Characters.RemoveRange(cursor.X, len);
            Invalid(CursorLineIndex);

        }

        public Line this[int index]
        {
            get
            {
                return (Line)list[index];
            }
        }

        public void EraseCharacter(Character character)
        {
            //character.background = defaultBackground;
            //character.foreground = defaultForeground;
            character.c = ' ';
            character.bg = cursor.BackgroundColor;
            character.fg = cursor.ForegroundColor;
            character.mask = CharacterMask.Empty;
        }
        public void EraseCharacters(int n, int x)
        {

        }
        public void EraseCharacters(int n)
        {
            Line line = CurrentLine();
            for (int i = CursorIndex; i < CursorIndex + n; i++)
            {
                //EraseCharacter(line[i]);
                EraseCharacter(line.GetCharacter(i));
            }
            Invalid(CursorLineIndex);
        }
        public void RemoveCursorToEnd()
        {

            Line line = CurrentLine();
            if (CursorIndex < line.Characters.Count)
            {
                line.Characters.RemoveRange(CursorIndex, line.Characters.Count - CursorIndex);
            }
            line.GetCharacter(cursor.X + 1).bg = cursor.BackgroundColor;
            line.GetCharacter(cursor.X + 1).fg = cursor.ForegroundColor;
            Invalid(cursor.Y);

        }

        public void FullCharacter(Character character, char cc, byte mask)
        {
            character.c = cc;
            character.mask = mask;

            character.mask |= (byte)cursor.CharacterStyle;

            if (cursor.RevolvedVideo)
            {
                character.fg = cursor.BackgroundColor;
                character.bg = cursor.ForegroundColor;
            }
            else
            {
                character.fg = cursor.ForegroundColor;
                character.bg = cursor.BackgroundColor;
            }
        }

        public TerminalColor defaultBackground = new TerminalColor(Preference.Default.Terminal_BackColor);
        public TerminalColor defaultForeground = new TerminalColor(Preference.Default.Terminal_ForeColor);

        public void SetDefaultForeground(Color color)
        {
            defaultForeground.color = color;
        }
        public void SetDefaultBackground(Color color)
        {
            defaultBackground.color = color;
        }

        public void ResetForeground()
        {
            cursor.ForegroundColor = defaultForeground;
        }

        public void ResetBackground()
        {
            cursor.BackgroundColor = defaultBackground;
        }

        public void SetForegroundColor(int color)
        {
            cursor.ForegroundColor = cursor.HeightLight ? TerminalColor.GetLighColor(color) :
                TerminalColor.GetColor(color);
        }

        public void SetForegroundRgb(byte r, byte g, byte b)
        {
            cursor.ForegroundColor = new TerminalColor(Color.FromArgb(r, g, b));
        }

        public void SetBackgroundColor(int color)
        {
            cursor.BackgroundColor = TerminalColor.GetColor(color);
        }
        public void SetBackgroundRgb(byte r, byte g, byte b)
        {
            cursor.BackgroundColor = new TerminalColor(Color.FromArgb(r, g, b));
        }

        public void SetCharacterStyle(int style)
        {
            cursor.CharacterStyle |= style;
        }

        public void UnsetCharacterStyle(int style)
        {
            cursor.CharacterStyle &= ~style;
        }

        public bool HeightLight
        {
            set
            {
                cursor.HeightLight = value;
            }
        }

        internal CursorAttributes Cursor { get => cursor; private set => cursor = value; }

        private int saveCursorLineIndex = 0;
        private int saveCursorIndex = 0;

        public void SaveCursorState()
        {
            saveCursorLineIndex = CursorLineIndex;
            saveCursorIndex = CursorIndex;
        }
        public void RestoreCursorState()
        {
            CursorIndex = saveCursorIndex;
            CursorLineIndex = saveCursorLineIndex;
        }
    }
}
